#include "Geometry.h"

using namespace std;
using namespace CICore;

///<************************************************************************************************************>
///<************************************* region CONSTANTS DEFINITION ******************************************>
///<************************************************************************************************************>

const CICore::Color CIGeom::Geometry::VERTEX_SELECTED_COLOR = Color (0, 0, 1);
const CICore::Color CIGeom::Geometry::FACE_SELECTED_COLOR = Color (1, 0, 0);
const CICore::Color CIGeom::Geometry::VERTEX_UNSELECTED_COLOR = Color (1.0f, 0.6f, 0.0f);
const CICore::Color CIGeom::Geometry::FACE_UNSELECTED_COLOR = Color (0.8f, 0.8f, 0.8f);
const CICore::Color CIGeom::Geometry::FACE_DEFAULT_FLAT_COLOR = Color (0.3f, 0.3f, 0.3f);
const float CIGeom::Geometry::POINT_SIZE = 5.0f;
const float CIGeom::Geometry::LINE_WIDTH = 1.0f;

/***************************************************************************************************************
**************************************** endregion CONSTANTS DEFINITION ****************************************
****************************************************************************************************************/








/// <summary>
/// Constructor
/// </summary>
/// <param name="n"> The geometry name </param>
CIGeom::Geometry::Geometry (const char* n) : name (n),
    hasPrimitivesSelected (true), hasPrimitivesHidden (false) {}

/// <summary>
/// Constructor
/// </summary>
/// <param name="n"> The geometry name </param>
CIGeom::Geometry::Geometry (const string& n) : name (n),
    hasPrimitivesSelected (true), hasPrimitivesHidden (false) {}

/// <summary>
/// Destructor
/// </summary>
CIGeom::Geometry::~Geometry() {
    DeleteAllVertices();    //Deletes all geometry vertices and faces
}








///<************************************************************************************************************>
///<**************************************** region Utility Methods ********************************************>
///<************************************************************************************************************>

/// <summary>
/// Method called to update the geometry's bounding box
/// </summary>
void CIGeom::Geometry::UpdateBoundingBox () {
    //Updating the box to contain all geometry's vertices
    VertexIter v = vertices.begin();
    
   while (v != vertices.end() ) 
        boundingBox.Update (*(*v++));       //Updates the bounding box so that it contains the vertex pointed by it            
}            

/// <summary>
/// Method called to apply a matrix transformation
/// </summary>
/// <param name="m"> The matrix to apply </param>
void CIGeom::Geometry::ApplyMatrix (const Matrix& m) {
    if (!hasPrimitivesSelected)
        return;    

    VertexIter v = vertices.begin();

    //Transforming all vertices
    while (v != vertices.end()) {
        if ((*v)->selected) 
            (*v)->Transform (m);                //transformes the vertex 

        (*v)->normal = Vector3D();              //resets the normal that will be recomputed

        v++;
    }

    //Computing all normals
    map<Vertex*, int> counter;
    FaceIter f = faces.begin();

    while (f != faces.end()){
        (*f)->ComputeNormal();                  //computes the face normal
        
        for (int i=0; i<(*f)->vertices.size(); i++) {
            (*f)->vertices[i]->normal += (*f)->normal;
            counter[(*f)->vertices[i]]++;
        }

        f++;
    }

    v = vertices.begin();

    while (v != vertices.end()) {      
        (*v)->normal /= counter[*v];
        (*v)->normal.Normalize();
        v++;
    }
}

/// <summary>
/// Method called to apply a matrix transformation
/// </summary>
void CIGeom::Geometry::ComputeNormals() {
    VertexIter v = vertices.begin();

    //Reseting the normal of the vertices
    while (v != vertices.end()) {
        (*v)->normal = Vector3D();              //resets the normal that will be recomputed

        v++;
    }

    //Computing all normals
    map<Vertex*, int> counter;
    FaceIter f = faces.begin();

    while (f != faces.end()){
        (*f)->ComputeNormal();                  //computes the face normal
        
        for (int i=0; i<(*f)->vertices.size(); i++) {
            (*f)->vertices[i]->normal += (*f)->normal;
            counter[(*f)->vertices[i]]++;
        }

        f++;
    }

    v = vertices.begin();

    while (v != vertices.end()) {      
        (*v)->normal /= counter[*v];
        (*v)->normal.Normalize();
        v++;
    }
}
  
/// <summary>
/// Method called to add a vertex to the geometry vertices
/// </summary>
/// <param name="x"> The x-axis coordinate </param>
/// <param name="y"> The y-axis coordinate </param>
/// <param name="z"> The z-axis coordinate </param>
/// <returns> The index of the vertex into the geometry vertices library </returns>
int CIGeom::Geometry::AddVertex (const float x, const float y, const float z) {
    unsigned int i = 0;
    const float errorMargin = 0.00001;

    //Checking if an equal vertex is already into the geometry vertices library
    for (; i < vertices.size(); i++) {   
        if ( abs (vertices[i]->x - x) < errorMargin 
            && abs (vertices[i]->y - y) < errorMargin 
            && abs (vertices[i]->z - z) < errorMargin)
                return i;       //Returns the vertex index inside the vertices library
    }

    vertices.push_back (new Vertex (x,y,z));    //Adds the new vertex    
    return i;                                   //Returns the vertex index inside the vertices library
}

/// <summary>
/// Method called to add a vertex to the geometry vertices
/// </summary>
/// <param name="x"> The vertex to add </param>
/// <returns> The index of the vertex into the geometry vertices library </returns>
int CIGeom::Geometry::AddVertex (Vertex* v) {
    unsigned int i = 0;

    //Checking if an equal vertex is already into the geometry vertices library
    for (; i < vertices.size(); i++)        
        if ((*vertices[i]) == (*v))
            return i;          //Returns the vertex index inside the vertices library

    vertices.push_back (v);    //Adds the new vertex
    return i;                  //Returns the vertex index inside the vertices library
}

/// <summary>
/// Method called to add a face to the geometry faces
/// </summary>
/// <param name="x"> The face to add </param>
/// <returns> The index of the face into the geometry faces library </returns>
void CIGeom::Geometry::AddFace (Face* f) {
    faces.push_back (f);
}

/// <summary>
/// Method called to delete all geometry vertices and faces
/// </summary>
void CIGeom::Geometry::DeleteAllVertices() {
    //Deleting all vertices
    VertexIter v_it = vertices.begin();

    while (v_it != vertices.end())
        delete (*v_it++);
    vertices.clear();

    //Deleting all faces
    FaceIter f_it = faces.begin();

    while (f_it != faces.end())
        delete (*f_it++);
    faces.clear();
}

/// <summary>
/// Method called to get a reference to the geometry name
/// </summary>
/// <returns> The geometry name </returns>
string& CIGeom::Geometry::GetName() {
    return name;
}

/// <summary>
/// Method called to get a reference to the geometry vertices
/// </summary>
/// <returns> The geometry vertices </returns>
VertexLib& CIGeom::Geometry::GetVertices() {
    return vertices;
}

/// <summary>
/// Method called to get a reference to the geometry faces
/// </summary>
/// <returns> The geometry faces </returns>
FaceLib& CIGeom::Geometry::GetFaces() {
    return faces;
}

/// <summary>
/// Method called to set the geometry vertex library
/// </summary>
/// <param name="vs"> The new geometry vertices </param>
void CIGeom::Geometry::SetVertices (VertexLib& vs) {
    vertices = vs;
}

/// <summary>
/// Method called to set the geometry face library
/// </summary>
/// <param name="fs"> The new geometry faces </param>
void CIGeom::Geometry::SetFaces (FaceLib& fs) {
    faces = fs;
}

/// <summary>
/// Method called to select all geometry vertices
/// </summary>
void CIGeom::Geometry::SelectAllVertices() {
    VertexIter it = vertices.begin();

    while (it != vertices.end()) {
        if ((*it)->visible)
            (*it)->selected = true;
        it++;
    }
    hasPrimitivesSelected = true;
}

/// <summary>
/// Method called to deselect all geometry vertices
/// </summary>
void CIGeom::Geometry::DeselectAllVertices() {
    if (!hasPrimitivesSelected)
        return;

    VertexIter it = vertices.begin();

    while (it != vertices.end()) {
        (*it)->selected = false;
        it++;
    }

    hasPrimitivesSelected = false;
}

/// <summary>
/// invert the current vertices selection status
/// </summary>
void CIGeom::Geometry::InvertVerticesSelection() {
    if (!hasPrimitivesSelected) {
        SelectAllVertices();
        return;
    }
    VertexIter it = vertices.begin();

    while (it != vertices.end()) {
        if ((*it)->visible)
            (*it)->selected = !((*it)->selected);

        if ((*it)->selected && !hasPrimitivesSelected)
            hasPrimitivesSelected = true;
        it++;
    }
}

/// <summary>
/// Method called to select all geometry faces
/// </summary>
void CIGeom::Geometry::SelectAllFaces() {
    SelectAllVertices();                //Selects all vertices
    
    //Selecting all faces
    FaceIter it = faces.begin();

    while (it != faces.end()) {
        if ((*it)->visible)
            (*it)->selected = true;
        it++;
    }
    hasPrimitivesSelected = true;
}

/// <summary>
/// Method called to deselect all geometry faces
/// </summary>
void CIGeom::Geometry::DeselectAllFaces() {
    if (!hasPrimitivesSelected)
        return;

    DeselectAllVertices();              //Deselects all vertices
    
    //Deselecting all faces
    FaceIter it = faces.begin();

    while (it != faces.end()) {
        (*it)->selected = false;
        it++;
    }

    hasPrimitivesSelected = false;
}

/// <summary>
/// Invert the current faces selection status
/// </summary>
void CIGeom::Geometry::InvertFacesSelection() {
    if (!hasPrimitivesSelected) {
        SelectAllFaces();
        return;
    }

    FaceIter it = faces.begin();

    //Smart faces selection inverter     
     while (it != faces.end()) {                                            //For each face         
         if ((*it)->visible) {
            if (!((*it)->selected))                                             //if is not selected
                for (unsigned short i = 0; i < (*it)->vertices.size(); i++)     //must deselect all its vertices
                    (*it)->vertices[i]->selected = false;                       //deselects the current vertex

            (*it)->selected = !((*it)->selected);   //Inverts the face selection status
         }
        it++;
    }

    InvertVerticesSelection();                 //Now we can invert the vertices selection
}


/// <summary>
/// Method called to hide the selected vertices
/// </summary>
void CIGeom::Geometry::HideSelectedVertices() {
    if (!hasPrimitivesSelected)
        return;

    VertexIter it = vertices.begin();

    while (it != vertices.end()) {          //For each vertex
        if ((*it)->selected) {              //if is selected
            (*it)->selected = false;        //deselects the vertex
            (*it)->visible = false;         //hides the vertex
        }
        it++;
    }
        
    FaceIter f = faces.begin();

    while (f != faces.end()) {                                              //For each face         
        if ((*f)->visible) {                                                //if visible
            for (unsigned short i = 0; i < (*f)->vertices.size(); i++)      //if at least a vertex of the face is not visible, must hide the face
                if (!(*f)->vertices[i]->visible) {                          
                    (*f)->visible = false;                      
                    break;
                }
        }
        f++;
    }

    hasPrimitivesSelected = false;
    hasPrimitivesHidden = true;
}

/// <summary>
/// Method called to hide the selected faces
/// </summary>
void CIGeom::Geometry::HideSelectedFaces() {
    if (!hasPrimitivesSelected)
        return;

    map<Vertex*,bool> verticesVisibility;

    //Hiding all selected faces
    FaceIter it = faces.begin();

    while (it != faces.end()) {             //For each face
        if ((*it)->selected) {              //if the face is selected
            (*it)->selected = false;        //deselects the face
            (*it)->visible = false;         //hides the face
            
            //each face vertex must be set to not visible
            for (int i=0; i<(*it)->vertices.size(); i++)
                verticesVisibility[(*it)->vertices[i]];
        }
        else if ((*it)->visible) {  //else if is visible, each face vertex must be set to visible            
            for (int i=0; i<(*it)->vertices.size(); i++)
                verticesVisibility[(*it)->vertices[i]] = true;
        }

        it++;
    }

    //Setting vertices visibility, according to faces visibility
    map<Vertex*,bool>::iterator v = verticesVisibility.begin();
    while (v != verticesVisibility.end()) {
        v->first->visible = v->second;
        v->first->selected = false;
        v++;
    }
    
    hasPrimitivesSelected = false;
    hasPrimitivesHidden = true;
}

/// <summary>
/// Method called to unhide all primitives
/// </summary>
void CIGeom::Geometry::UnhideAll() {
    if (!hasPrimitivesHidden)
        return;

    FaceIter f = faces.begin();

    while (f != faces.end()) {         //For each face
        if (!(*f)->visible) {
            (*f)->visible = true;           //unhides the face
            (*f)->selected = true;          //selects the face
        }
        f++;
    }

    VertexIter v = vertices.begin();

    while (v != vertices.end()) {      //For each vertex
        if (!(*v)->visible) {
            (*v)->visible = true;           //unhides the vertex
            (*v)->selected = true;          //selects the vertex
        }
        v++;
    }

    hasPrimitivesHidden = false;
    hasPrimitivesSelected = true;
}
/***************************************************************************************************************
******************************************* endregion Utility Methods ******************************************
****************************************************************************************************************/
